-- LSP: Auto-format on save
-- https://github.com/rafi/vim-config

-- This is part of LazyVim's code, with my modifications.
-- See: https://github.com/LazyVim/LazyVim/blob/main/lua/lazyvim/plugins/lsp/format.lua

local M = {}

---@type PluginLspOpts
M.opts = nil

function M.enabled()
	return M.opts.autoformat
end

function M.toggle()
	if vim.b.autoformat == false then
		vim.b.autoformat = nil
		M.opts.autoformat = true
	else
		M.opts.autoformat = not M.opts.autoformat
	end
	local msg = M.opts.autoformat and 'Enabled' or 'Disabled'
	vim.notify(
		msg .. ' format on save',
		vim.log.levels.INFO,
		{ title = 'Format' }
	)
end

---@param opts? {force?:boolean}
function M.format(opts)
	local buf = vim.api.nvim_get_current_buf()
	if vim.b.autoformat == false and not (opts and opts.force) then
		return
	end

	local formatters = M.get_formatters(buf)
	local client_ids = vim.tbl_map(function(client)
		return client.id
	end, formatters.active)

	if #client_ids == 0 then
		return
	end

	if M.opts.format_notify then
		M.notify(formatters)
	end

	vim.lsp.buf.format(vim.tbl_deep_extend('force', {
		bufnr = buf,
		filter = function(client)
			return vim.tbl_contains(client_ids, client.id)
		end,
	}, require('rafi.lib.utils').opts('nvim-lspconfig').format or {}))
end

---@param formatters LazyVimFormatters
function M.notify(formatters)
	local lines = { '# Active:' }

	for _, client in ipairs(formatters.active) do
		local line = '- **' .. client.name .. '**'
		if client.name == 'null-ls' then
			line = line
				.. ' ('
				.. table.concat(
					vim.tbl_map(function(f)
						return '`' .. f.name .. '`'
					end, formatters.null_ls),
					', '
				)
				.. ')'
		end
		table.insert(lines, line)
	end

	if #formatters.available > 0 then
		table.insert(lines, '')
		table.insert(lines, '# Disabled:')
		for _, client in ipairs(formatters.available) do
			table.insert(lines, '- **' .. client.name .. '**')
		end
	end

	vim.notify(table.concat(lines, '\n'), vim.log.levels.INFO, {
		title = 'Formatting',
		on_open = function(win)
			local scope = { scope = 'local', win = win }
			vim.api.nvim_set_option_value('conceallevel', 3, scope)
			vim.api.nvim_set_option_value('spell', false, scope)
			local buf = vim.api.nvim_win_get_buf(win)
			vim.treesitter.start(buf, 'markdown')
		end,
	})
end

-- Gets all lsp clients that support formatting.
-- When a null-ls formatter is available for the current filetype,
-- only null-ls formatters are returned.
function M.get_formatters(bufnr)
	local ft = vim.bo[bufnr].filetype
	-- check if we have any null-ls formatters for the current filetype
	local null_ls = package.loaded['null-ls']
			and require('null-ls.sources').get_available(ft, 'NULL_LS_FORMATTING')
		or {}

	---@class LazyVimFormatters
	local ret = {
		---@type lsp.Client[]
		active = {},
		---@type lsp.Client[]
		available = {},
		null_ls = null_ls,
	}

	local clients
	if vim.lsp.get_clients ~= nil then
		clients = vim.lsp.get_clients({ bufnr = bufnr })
	else
		---@diagnostic disable-next-line: deprecated
		clients = vim.lsp.get_active_clients({ bufnr = bufnr })
	end
	for _, client in ipairs(clients) do
		if M.supports_format(client) then
			if (#null_ls > 0 and client.name == 'null-ls') or #null_ls == 0 then
				table.insert(ret.active, client)
			else
				table.insert(ret.available, client)
			end
		end
	end

	return ret
end

-- Gets all lsp clients that support formatting
-- and have not disabled it in their client config
---@param client lsp.Client
function M.supports_format(client)
	if
		client.config
		and client.config.capabilities
		and client.config.capabilities['documentFormattingProvider'] == false
	then
		return false
	end
	return client.supports_method('textDocument/formatting')
		or client.supports_method('textDocument/rangeFormatting')
end

---@param opts PluginLspOpts
function M.setup(opts)
	M.opts = opts
	vim.api.nvim_create_autocmd('BufWritePre', {
		group = vim.api.nvim_create_augroup('rafi_format', {}),
		callback = function()
			if M.opts.autoformat then
				M.format()
			end
		end,
	})
end

return M
